const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`; // abc-aaa //转译-(0-9的-代表'到'的意思) 和 .(任何)
const qnameCapture = `((?:${ncname}\\:)?${ncname})`; // aaa:asdads //()就会捕获,但我们常常为了使用`+?`等量词,需要使用`()`进行包裹,故为了避免被捕获,可以再`()`里使用`?:`
const startTagOpen = new RegExp(`^<${qnameCapture}`); // 标签开头的正则 捕获的内容是标签名
//console.log('<div:aa>'.match(startTagOpen)); //div:aa这种是命名空间标签 //[ '<div:aa'/*匹配到的内容*/, 'div:aa'/*←匹配到的捕获结果*/, index: 0, input: '<div:aa>', groups: undefined ]

const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`); // 匹配标签结尾的 </div>
// '</div:xxx>'.match(endTag)
/*
[
  '</div:xxx>',
  'div:xxx',
  index: 0,
  input: '</div:xxx>',
  groups: undefined
]
*/

// 匹配属性
// \s ---> 空格
// id（允许有空格）=（允许有空格）"fwfwfsf"
// id（允许有空格）=（允许有空格）'fwfwfsf'
// id（允许有空格）=（允许有空格）xxxx
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/; // 匹配属性的
// console.log(`a='1'`.match(attribute))
// console.log(`a="1"`.match(attribute))
// console.log(`a=1`.match(attribute))
/*
[
  "a='1'",
  'a',
  '=',
  undefined,
  '1',
  undefined,
  index: 0,
  input: "a='1'",
  groups: undefined
]
[
  'a="1"',
  'a',
  '=',
  '1',
  undefined,
  undefined,
  index: 0,
  input: 'a="1"',
  groups: undefined
]
[
  'a=1',
  'a',
  '=',
  undefined,
  undefined,
  '1',
  index: 0,
  input: 'a=1',
  groups: undefined
]
*/


// div（允许有空格）/> 中的 />
//   div（允许有空格）> 中的 >
const startTagClose = /^\s*(\/?)>/; // 匹配起始标签结束的 > 如果是单标签可能还会有/
//console.log(/^.{3}$/.test("   ")) //.能匹配空格

//{{}}
//
//{{（允许换行）
//  aaa
//}}
export const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g;

//怎么解析？
//步进：每解析一块就删掉一块,边解析边删除
export function parseHTML(html) {
  //每次调parseHTML 都生成一颗全新的ast树
  let root = null; // ast语法树树根
  let currentParent; // 标识当前父亲是谁
  let stack = [];
  const ELEMENT_TYPE = 1;
  const TEXT_TYPE = 3;

  // console.log('parseHTML html:',html);
  // 不停的去解析html字符串
  while (html) {
    const textEnd = html.indexOf('<');

    // 说明是标签
    // 可能是开始标签 也有可能是结束标签
    if(textEnd == 0){// 如果当前索引为0 肯定是一个标签

      let startTagMatch = parseStartTag(); // 通过这个方法获取到匹配的结果 tagName,attrs 里面会会步进 直到"扫"完整个起始标签

      //是个开始标签
      if(startTagMatch){
        start(startTagMatch.tagName, startTagMatch.attrs);
        continue; // 进入下一个while循环
      }

      //是个结尾标签
      const endTagMatch = html.match(endTag);
      if(endTagMatch){
        advance(endTagMatch[0].length);
        end(endTagMatch[1]);
        continue;
      }
    }

    // 说明标签之间存在文本
    // <div>123<div>
    let text;
    if (textEnd > 0) {
      text = html.substring(0, textEnd);
    }
    if (text) {
      advance(text.length);
      chars(text);
    }
  }

  function parseStartTag(){
    const start = html.match(startTagOpen);
    // console.log('start:',start);

    if(start){
      const match = {
        tagName: start[1], //读取到标签名了
        attrs: []
      };
      advance(start[0].length); // 向前进 将当前读到的<xxx从html中删除

      //开始尝试读取html属性
      let end, attr;
      while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))){
        advance(attr[0].length); // 向前进 将当前读到的属性pair从html删除
        match.attrs.push({
          name: attr[1],
          value: attr[3]/*a="1"的1*/ || attr[4]/*a='1'的1*/ || attr[5]/*a=1的1*/||true/*没有写属性值*/
        })
      }

      // console.log('match:', match);
      if(end){ // 去掉开始标签的 >
        advance(end[0].length);
        return match;
      }
    }
  }

  //advance 前进
  //删掉已经读取过的字符
  function advance(n){
    html = html.substring(n);
    // console.log('advance html:',html);
  }

  // 遇到开始标签 就创建一个ast元素
  function start(tagName,attrs){
    let element = createASTElement(tagName, attrs);
    if(!root){
      root = element;
    }
    currentParent = element; // div>p>span
    stack.push(element); // 将开始标签存放到栈中 这个栈结构有助于我们在end()时知道弹出的标签是属于哪个的子标签
  }

  /** 当遇到 /> 时会执行以下end函数
   * 主要目的是构建ast的父子关系*/
  // <div><p><span></span></p></div>
  // ↑当扫描到 '<div><p><span></span>'
  // stack里的情况是这样的 [div(←ast),p(←ast),span(←ast)]
  // 此时扫描到 </span>
  // 触发end(tagName)
  // tagName ---> span
  function end(tagName){
    const element = stack.pop(); //span(←ast) //儿子

    currentParent = stack[stack.length - 1]; //p(←ast)
    if (currentParent) {
      element.parent = currentParent; // span(←ast).parent = p(←ast)
      currentParent.children.push(element); // p(←ast).children = [span(←ast)]
    }
  }

  function chars(text){
    text = text.replace(/\s/g, ''); //源码里是将多个空格变成一个, 我们这里不要了
    if (text) {
      currentParent.children.push({
        text,
        type: TEXT_TYPE
      });
    }
  }

  function createASTElement(tagName,attrs) {
    return {
      tag: tagName,
      type: ELEMENT_TYPE,
      children: [],
      attrs,
      parent: null
    };
  }

  return root;
}
